package sf.jooq;

import org.jooq.Attachable;
import org.jooq.Condition;
import org.jooq.Configuration;
import org.jooq.Delete;
import org.jooq.Insert;
import org.jooq.Record;
import org.jooq.SQLDialect;
import org.jooq.Select;
import org.jooq.Table;
import org.jooq.TableField;
import org.jooq.UniqueKey;
import org.jooq.Update;
import org.jooq.impl.DSL;
import org.jooq.impl.DefaultConfiguration;
import sf.common.wrapper.Page;
import sf.core.DBObject;
import sf.database.dao.DBContext;
import sf.database.dialect.DBDialect;
import sf.database.jdbc.sql.Crud;
import sf.database.jdbc.sql.PageStrategy;
import sf.database.util.DBUtils;
import sf.jooq.tables.JooqTable;
import sf.spring.util.CollectionUtils;

import java.sql.Connection;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.function.Consumer;
import java.util.stream.Stream;

public class JooqImpl implements JooqInf {
    private static JooqImpl instance = new JooqImpl();

    public static JooqImpl getInstance() {
        return instance;
    }

    private JooqImpl() {

    }

    @Override
    public <T extends DBObject> boolean jooqExists(Connection conn, SQLDialect sqlDialect, List<Condition> condition, Class<T> clz) {
        DBDialect dialect = DBUtils.doGetDialect(conn, false);
        DBContext context = DBUtils.doGetDBContext(conn);
        JooqTable table = JooqTables.getTable(context, clz, dialect);
        if (sqlDialect == null) {
            sqlDialect = JooqDialectUtils.getSQLDialect(dialect.getJooqDialect());
        }
        if (sqlDialect == null) {
            sqlDialect = SQLDialect.DEFAULT;
        }
        Select select = null;
        if (CollectionUtils.isNotEmpty(condition)) {
            select = DSL.using(sqlDialect).select(DSL.field("1")).from(table).where(condition).limit(1);
        } else {
            select = DSL.using(sqlDialect).select(DSL.field("1")).from(table).limit(1);
        }
        String sql = select.getSQL();
        List<Object> values = select.getBindValues();
        Byte b = Crud.getInstance().getCrudSql().selectOne(conn, Byte.class, sql, values.toArray());
        return b != null && b > 0;
    }

    @Override
    public <T> T jooqSelectOne(Connection conn, Select select, Class<T> returnClass) {
        setDialect(conn, select);
        String sql = select.getSQL();
        List<Object> values = select.getBindValues();
        return Crud.getInstance().getCrudSql().selectOne(conn, returnClass, sql, values.toArray());
    }

    @Override
    public <T> List<T> jooqSelectList(Connection conn, Select select, Class<T> returnClass) {
        setDialect(conn, select);
        String sql = select.getSQL();
        List<Object> values = select.getBindValues();
        return Crud.getInstance().getCrudSql().selectList(conn, returnClass, sql, values.toArray());
    }

    @Override
    public <T> Page<T> jooqSelectPage(Connection conn, Select<?> countSelect, Select<?> pageSelect, Class<T> returnClass) {
        setDialect(conn, countSelect);
        setDialect(conn, pageSelect);
        String sql = pageSelect.getSQL();
        List<Object> values = pageSelect.getBindValues();
        return Crud.getInstance().getCrudSql().selectPageRaw(conn, 0, 0, (Class<T>) returnClass, countSelect.getSQL(), countSelect.getBindValues().toArray(), sql, values.toArray(), PageStrategy.hasOffsetLimit);
    }

    @Override
    public <T> void selectIterator(Connection conn, Consumer<Iterable<T>> ormIt, Select select, Class<T> returnClass) {
        setDialect(conn, select);
        String sql = select.getSQL();
        List<Object> values = select.getBindValues();
        Crud.getInstance().getCrudSql().selectIterator(conn, ormIt, returnClass, false, sql, values.toArray());
    }

    @Override
    public <T> void selectStream(Connection conn, Consumer<Stream<T>> ormStream, Select select, Class<T> returnClass) {
        setDialect(conn, select);
        String sql = select.getSQL();
        List<Object> values = select.getBindValues();
        Crud.getInstance().getCrudSql().selectStream(conn, ormStream, returnClass, false, sql, values.toArray());
    }

    private void setDialect(Connection conn, Attachable select) {
        Configuration configuration = select.configuration();
        if (configuration == null) {
            configuration = new DefaultConfiguration();
            select.attach(configuration);
        }
        if (configuration.dialect() == SQLDialect.DEFAULT) {
            SQLDialect sqlDialect = JooqDialectUtils.getSQLDialect(DBUtils.doGetDialect(conn, false).getJooqDialect());
            if (sqlDialect != null) {
                //需要从连接中动态获取dialect
                configuration.set(sqlDialect);
            }
        }
    }

    @Override
    public <T extends Record> int jooqInsert(Connection conn, Insert<T> insert) {
        setDialect(conn, insert);
        String sql = insert.getSQL();
        List<Object> values = insert.getBindValues();
        return Crud.getInstance().getCrudSql().execute(conn, sql, values.toArray());
    }

    @Override
    public <T extends Record> int jooqInsert(Connection conn, Insert<T> insert, Table<T> table, Map<String, Object> keyValues) {
        setDialect(conn, insert);
        String sql = insert.getSQL();
        List<Object> values = insert.getBindValues();
        List<String> pkeys = new ArrayList<>();

        if (table != null && table.getPrimaryKey() != null) {
            UniqueKey<T> uk = table.getPrimaryKey();
            if (uk != null) {
                List<TableField<T, ?>> list = uk.getFields();
                if (CollectionUtils.isNotEmpty(list)) {
                    for (TableField<T, ?> f : list) {
                        pkeys.add(f.getName());
                    }
                }
            }
        }
        return Crud.getInstance().getCrudSql().execute(conn, sql, values.toArray(), false, pkeys, keyValues);
    }

    @Override
    public <T extends Record> int jooqUpdate(Connection conn, Update<T> update) {
        setDialect(conn, update);
        String sql = update.getSQL();
        List<Object> values = update.getBindValues();
        return Crud.getInstance().getCrudSql().execute(conn, sql, values.toArray());
    }

    @Override
    public <T extends Record> int jooqDelect(Connection conn, Delete<T> delete) {
        setDialect(conn, delete);
        String sql = delete.getSQL();
        List<Object> values = delete.getBindValues();
        return Crud.getInstance().getCrudSql().execute(conn, sql, values.toArray());
    }
}
